# Samba mixer
# uses samples kindly provided by RoR Berlin

import wave, struct, numpy
from backend import mainPath

FRAMERATE = 44100
class Sample:
    "Instrument sample (assumes RoR Berlin format)"
    def __init__(self, source):
        "Initialize object with file named source"
        self.name = source
        W = wave.open(mainPath+source, 'rb')
        nchannels, sampwidth, framerate, nframes, comptype, compname = W.getparams()
        if nchannels != 1:
            raise wave.Error('nchannels != 1', nchannels)
        if sampwidth != 2:
            raise wave.Error('sampwidth != 2', sampwidth)
        if framerate != FRAMERATE:
            raise wave.Error('framerate != ' + str(FRAMERATE), framerate)
        if comptype != 'NONE':
            raise wave.Error('comptype != NONE', comptype)
        frames = W.readframes(nframes)
        self.frames = numpy.array(struct.unpack('<' + str(nframes) + 'h', frames))
    def __repr__(self):
        return "Sample('%s')" % self.name

MIXER_SAMPLES = dict([])
class Mixer:
    "Mixer class"
    def __init__(self, tune):
        """Initialize mixer with the tune;
        The tune is an array of the form (time [in seconds], sample_name, magnitude)"""
        self.tune = tune
        self.instruments = dict([])
        fade_outs = [] # time when each instrument finally fades out
        for sample in set([s[1] for s in tune]):
            if not sample in MIXER_SAMPLES:
                MIXER_SAMPLES[sample] = Sample(sample)
            instrument = sample.split('.')[0].split('_')[0]
            self.instruments[sample] = instrument
            length = len(MIXER_SAMPLES[sample].frames)
            fade_outs.append(length + max([int(s[0] * FRAMERATE) for s in tune if s[1] == sample]))
        self.nframes = max(fade_outs)
    def instrument_list(self):
        "List of instruments used in the tune"
        return sorted(self.forces.keys())
    def total_time(self):
        "Return total time in seconds"
        return float(self.nframes)/FRAMERATE
    def mix(self, filename, instrument = None):
        "Mix tune into a WAV, optionally only a single part"
        frames = numpy.zeros(self.nframes, dtype=int)
        for t, sample_name, magnitude in self.tune:
            sample = MIXER_SAMPLES[sample_name]
            t0 = int(t * FRAMERATE)
            t1 = t0 + len(sample.frames)
            inst = self.instruments[sample_name]
            if instrument is not None:
                if inst != instrument:
                    magnitude = 0
            if magnitude != 0:
                frames[t0:t1] += magnitude * sample.frames
        # equalize
        max_val = max(frames.max(), -frames.min())
        frames = (32766./max_val) * frames
        frames = frames.round()
        iframes = numpy.array(frames, dtype='int16')
        # write
        W = wave.open(filename, 'wb')
        W.setparams((1, 2, FRAMERATE, self.nframes, 'NONE', 'No Compression'))
        W.writeframes(iframes.data) # N.B. must be little-endian!!!
